#!/usr/bin/perl -w
# -*-perl -*-

# copyright 2004 - 2008, Jeremy Hinds <jeremy.hinds AT gmail DOT com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 
use strict;
use Getopt::Long;

my $version = '0.3.3';

my %supported_languages = ( 'c' => 1, 'cpp' => 1, 'c++' => 1 );

my ($show_help, $show_version, $name, $desc, $dir, $force, $use_autotools,
    $language);
$language = 'c';
&GetOptions("help"        => \$show_help,
            "version"     => \$show_version,
            "name:s"      => \$name,
            "shortdesc:s" => \$desc,
            "force"       => \$force,
            "dir:s"       => \$dir,
            "G|autotools" => \$use_autotools,
            "language=s"  => \$language,
           );

if ( $show_version ) {
  version();
  exit(1);
}
if ( $show_help || !defined($name) ) {
  usage();
  exit(1);
}

if ( ! defined($supported_languages{$language}) ) {
  die "language \"$language\" is unknown.  see $0 --help for information\n";
}

if ( $language eq 'c++' ) {
  # normalize to make comparisons easier later
  $language = 'cpp';
}


# files only generated when -G is specified.
# Makefile will not be gen'd with -G, but that's hard-coded below.
my %autotools_out = ( 'configure.in' => 1,
                      'Makefile.am'  => 1,
                      'NEWS'         => 1,
                      'README'       => 1,
                      'AUTHORS'      => 1,
                      'ChangeLog'    => 1,
                    );

# TODO: use perl's pathfind thing here
chomp(my $cgener_bin = `which cgener 2>/dev/null`);
if ( ! -e $cgener_bin ) {
  print STDERR "please make sure cgener is in your \$PATH and try again.\n";
  exit(1);
}

$desc = '' if ( ! defined($desc) );

if ( ! defined($dir) ) {
  $dir = './';
}

if ( ! -d $dir ) {
  print STDERR "creating directory $dir\n";
  mkdir($dir) or die "$dir: $!\n";
}

if ( $dir !~ m#/$# ) {
  $dir .= '/';
}

if ( ! -e "$dir/cgener" ) {
  print STDERR "creating symlink to \"$cgener_bin\"\n";
  symlink($cgener_bin, "$dir/cgener");
}

my @date = localtime(time);
my $current_year = $date[5] + 1900;

my $keep_file = 1;
while(<DATA>) {
  s/%appname%/$name/g;
  s/%appdesc%/$desc/g;
  s/%language%/$language/g;
  s/%current_year%/$current_year/g;
  if (/^----(.*)----$/) {
    # need to read all the lines whether or not this is a file we're keeping
    if ( $1 eq 'Makefile' && $use_autotools ) {
      $keep_file = 0;
    }
    elsif ( $autotools_out{$1} && ! $use_autotools ) {
      $keep_file = 0;
    }
    else {
      $keep_file = 1;
    }

    if ( ! $keep_file ) {
      next;
    }

    my $newfilename = "$dir$1";

    if ( defined(fileno(OUT)) ) {
      close(OUT);
    }

    if (-e $newfilename && ! $force) {
      print STDERR "$newfilename already exists.  ",
                   "use -f option to overwrite.\n";
      exit(1);
    }
    print STDERR "writing to $newfilename\n";
    open(OUT, "> $newfilename") or die "$newfilename: $!\n";
  } else {
    if ( $keep_file ) {
      print OUT $_;
    }
  }
}

close(OUT);

if ( $use_autotools ) {
  system('aclocal');
  system('autoconf');
  system('autoheader');
  system('automake', '-a');
}

exit(0);

sub usage {
  print STDERR <<DISCLAIMER;

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
DISCLAIMER

  print STDERR
      "\nnewcapp: makes a few files to get started on a new c application\n",
      "usage: $0 < -h | -n <name> > [-f] [ -s <description> -d <dir>]\n\n";
  print STDERR << 'DONE_OPTS';
  -h, --help             print this message and exit.
  -v, --version          print version number and exit.
  -n, --name <name>      specifies new application name.  (required)
  -s, --shortdesc <desc> description to go in application's usage msg.
                         (default: blank)
  -d, --directory <dir>  place to put the files (default: ./)
  -f, --force            force overwriting files that already exist
  -G, --autotools        generate autoconf/automake files
  -l, --language <L>     use language L - 'c', 'cpp', or 'c++' (default: 'c')

DONE_OPTS
}

sub version {
  print STDERR "$0: $version\n";
}

__DATA__
----%appname%.%language%----
/********************************
  copyright
 ********************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "%appname%_main.h"

/** @brief  
  * 
  * @param args contains the parsed cmd-line options & arguments.
  * @param argc number of cmd-line arguments.
  * @param argv list of cmd-line arguments
  * @param optind index of the first non-option cmd-line argument.
  * 
  * @return exit status for main() to return.
  */
int %appname% ( struct cmdargs *args, int argc, char *argv[], int optind ) {

  return EXIT_OKAY;
}

----args.tab----
# data relevant to the application as a whole
%global = (
  name          => "%appname%",
  description   => "%appdesc%",
  version       => "0.1",
  trailing_opts => "[file ...]",
  usage_extra   => "",
  do_long_opts  => 1,
  preproc_extra => '',
  language      => '%language%',
  copyright     => <<END_COPYRIGHT
copyright %current_year%
END_COPYRIGHT
);

# descriptions of individual command-line arguments
# valid types are
#  flag                - no argument
#  var                 - required argument
#  var_optional        - optional argument
#  custom_flag         - no arg, and specify parseopt_code 
#  custom_var          - required arg, and specify parseopt_code 
#  custom_var_optional - optional arg, and specify parseopt_code 
@args = ( {
    name        => 'help',
    shortopt    => 'h',
    longopt     => 'help',
    type        => 'flag',
    required    => 0,
    main_code   => 'usage(argv[0]); exit(EXIT_HELP);',
    description => 'print this message and exit'
  },
  {
    name        => 'version',
    shortopt    => 'V',
    longopt     => 'version',
    type        => 'flag',
    required    => 0,
    main_code   => 'version(); exit(1);',
    description => 'print version info and exit'
  },
  {
    name          => 'verbose',
    shortopt      => 'v',
    longopt       => 'verbose',
    type          => 'custom_flag',
    required      => 0,
    parseopt_code => 'args->verbose++;',
    description   => 'print verbose messages during execution'
  },
);
----Makefile----
EXECUTABLE := %appname%
DESCRIPTION := %appdesc%
VERSION = 0.1

DISTFILE = $(EXECUTABLE)-$(VERSION).tgz
# resources like README, COPYING, etc., which should be included in
# the distribution file
RESOURCES := Makefile args.tab cgener

WORKDIR := $(shell pwd)
CC := $(shell which gcc)
CXX := $(shell which g++)
ifeq (%language%,cpp)
  COMPILER=$(CXX)
else
  COMPILER=$(CC)
endif

MAINGENERATOR := ./cgener
ARGSTAB := args.tab
CFLAGS := -O2 -Wall
CXXFLAGS = $(CFLAGS)
LINKFLAGS :=

GEN_FILES := main.%language% usage.%language%
GEN_OBJECTS := $(patsubst %.%language%,%.o,$(GEN_FILES))
CFILES := $(filter-out $(GEN_FILES), $(wildcard *.c))
CXXFILES := $(filter-out $(GEN_FILES), $(wildcard *.cpp))
CHEADERS :=$(wildcard *.h)
OBJECTS := $(patsubst %.c,%.o,$(CFILES)) $(patsubst %.cpp,%.o,$(CXXFILES))
DEPS := $(patsubst %.o,%.d,$(OBJECTS))

.PHONY : deps clean dist

default : $(EXECUTABLE)

deps :  $(OBJECTS) $(GEN_OBJECTS)

clean ::
	rm -f *.o
	rm -f *.d
	rm -f $(GEN_FILES)

dist : $(DISTFILE)

$(DISTFILE) : $(CFILES) $(CXXFILES) $(GEN_FILES) $(CHEADERS) $(RESOURCES)
	@ls $(CFILES) $(CXXFILES) $(GEN_FILES) $(CHEADERS) $(RESOURCES) | sed s:^:$(EXECUTABLE)-$(VERSION)/: >MANIFEST
	@(cd ..; ln -s "$(WORKDIR)" $(EXECUTABLE)-$(VERSION))
	(cd ..; tar -czvf "$(WORKDIR)/$(DISTFILE)" `cat "$(WORKDIR)/MANIFEST"`)
	@(cd ..; rm $(EXECUTABLE)-$(VERSION))

$(GEN_OBJECTS) : $(GEN_FILES)

$(GEN_FILES) : $(ARGSTAB)
	$(MAINGENERATOR) -a $(ARGSTAB)

$(EXECUTABLE) : $(GEN_OBJECTS) $(OBJECTS)
	$(COMPILER) $(CFLAGS) -o $(EXECUTABLE) $(GEN_OBJECTS) $(OBJECTS) $(LINKFLAGS)


----Makefile.am----
bin_PROGRAMS = %appname%

BUILT_SOURCES = main.%language% usage.%language% %appname%_main.h

$(BUILT_SOURCES) : args.tab
	./cgener -a args.tab

%appname%_SOURCES = %appname%.%language% $(BUILT_SOURCES)

CLEANFILES = $(BUILT_SOURCES)

EXTRA_DIST = args.tab cgener

----configure.in----
AC_INIT(%appname%.%language%)
AM_CONFIG_HEADER(config.h)
AM_INIT_AUTOMAKE(%appname%, 0.1)
AC_PROG_CC
AC_PROG_CXX
AC_PROG_INSTALL

AC_CHECK_HEADERS(stdio.h stdlib.h string.h unistd.h)
dnl AC_CHECK_LIB()
AC_CHECK_FUNCS(getopt getopt_long)

AC_OUTPUT(Makefile)
----NEWS----
----README----
----AUTHORS----
----ChangeLog----

